/**********************************************************************//**
 @file manip.cpp 
 @brief Contains methods for canvas manipulation
 @author Ricardo C. Marques <rmarques@inf.puc-rio.br>
 @version 0.0.1
 @date 05/11/2012
**************************************************************************/

// Standard Libraries
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#undef   _CRT_SECURE_NO_WARNINGS

// Graphic Libraries
#define GLUT_DISABLE_ATEXIT_HACK
#include "GL/glut.h"
#undef  GLUT_DISABLE_ATEXIT_HACK

// Module Own Header File
#include "manip.hpp"

// Auxiliary Header Files
#include "draw.hpp"

// Constants
#define SCALE_FACTOR    0.1 // Zoom Increment factor
#define SHIFT_FACTOR    0.1 // Translation Increment

// Static Variables Declaration
/**************************************************************************/

// Mouse Action Flags
static bool isZooming=false;
static bool isPanning=false;

// Mouse Drag Initial and Final Positions 
static float xi, yi; // ( in window coordinates )
static float xf, yf;

// Zoom Stack Datatype
typedef struct stack
{
    // Zoom limits in world coordinates
    float xmin,ymin; 
    float xmax,ymax;
    struct stack* next;
} ZoomStack;
static ZoomStack* top=NULL;

// Function Declarations
/**************************************************************************/
static void MouseDrag(int winx, int winy);

// Function Definitions
/**************************************************************************/
// Zoom Functions 
static void Push(float   xmin, float   xmax, float   ymin, float   ymax )
{
    ZoomStack* P = (ZoomStack*)malloc(sizeof(ZoomStack));
    P->xmin = xmin;
    P->xmax = xmax;
    P->ymax = ymax;
    P->ymin = ymin;
    P->next = top;
    top = P;
}

static bool Pop (float*  xmin, float*  xmax, float*  ymin, float*  ymax )
{
    if(top==NULL)
    {
        //printf("Empty Zoom Stack\n");
        return false;
    }

    *xmin = top->xmin;
    *xmax = top->xmax;
    *ymin = top->ymin;
    *ymax = top->ymax;

    ZoomStack* P = top->next;
    free(top);
    top=P;

    return true;
}

static void Zoom(float win_x0, float win_y0, float win_x1, float win_y1 )
{
    if( win_x0==win_x1 || win_y0==win_y1)
        return;

    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    Push(xmin, xmax, ymin, ymax);

    if( win_x1<win_x0 )
    {
        float tmp = win_x0;
        win_x0 = win_x1;
        win_x1 = tmp;
    }
    if( win_y1>win_y0 )
    {
        float tmp = win_y0;
        win_y0 = win_y1;
        win_y1 = tmp;
    }

    RasterToWorld(win_x0, win_y0, &xmin, &ymin);
    RasterToWorld(win_x1, win_y1, &xmax, &ymax);

    SetVisualizationWindow(xmin, xmax, ymin, ymax);
}
static void DrawZoomBox(void)
{
    // Zoom Box is drawn only if zooming
    if( !isZooming )
        return;

    float x0, x1, y0, y1;
    RasterToWorld(xi, yi, &x0, &y0 );
    RasterToWorld(xf, yf, &x1, &y1 );

    glColor3f(1.f,1.f,1.f);
    glBegin(GL_LINE_LOOP);
        glVertex2f(x1,y1);
        glVertex2f(x1,y0);
        glVertex2f(x0,y0);
        glVertex2f(x0,y1);
    glEnd();
}
void StartZoom(float winx, float winy)
{
    isZooming = true;
    xi=winx;
    yi=winy;
    glutMotionFunc(MouseDrag);
    SetDrawEventFunc( DrawZoomBox );
    glutSetCursor(GLUT_CURSOR_FULL_CROSSHAIR);
}
void EndZoom(float winx, float winy)
{
    isZooming=false;
    xf=winx;
    yf=winy;
    Zoom(xi, yi, xf, yf);
    glutMotionFunc(NULL);
    SetDrawEventFunc(NULL);
    glutSetCursor(GLUT_CURSOR_INHERIT);
}

void RestoreLastZoom(void)
{
    float xmin, xmax, ymin, ymax;
    if( Pop(&xmin, &xmax, &ymin, &ymax) ) 
        SetVisualizationWindow(xmin, xmax, ymin, ymax);
}

void ZoomIn(void)
{
    glutSetCursor(GLUT_CURSOR_TOP_SIDE);
    float f = 1.+SCALE_FACTOR;
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow(f*xmin, f*xmax, f*ymin, f*ymax);
    glutSetCursor(GLUT_CURSOR_INHERIT);
}
void ZoomOut(void)
{
    glutSetCursor(GLUT_CURSOR_TOP_SIDE);
    float f = 1.-SCALE_FACTOR;
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow(f*xmin, f*xmax, f*ymin, f*ymax);
    glutSetCursor(GLUT_CURSOR_INHERIT);
}
// Panning Functions
static void Shift(float win_x0, float win_y0, float win_x1, float win_y1)
{
    float x0, x1, y0, y1;
    RasterToWorld(win_x0, win_y0, &x0, &y0);
    RasterToWorld(win_x1, win_y1, &x1, &y1);

    float dx = x1-x0;
    float dy = y1-y0;

    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(   &xmin,    &xmax,    &ymin,    &ymax);
    SetVisualizationWindow( xmin-dx,  xmax-dx,  ymin-dy,  ymax-dy);
}
void StartPanning(float winx, float winy)
{
    glutSetCursor(GLUT_CURSOR_INFO);
    isPanning = true;
    xi=winx;
    yi=winy;
    glutMotionFunc(MouseDrag);
}
void EndPanning(float winx, float winy)
{
    isPanning=false;
    glutMotionFunc(NULL);
    xf=winx;
    yf=winy;
    Shift(xi, yi, xf, yf);
    glutSetCursor(GLUT_CURSOR_INHERIT);
}
void ShiftLeft(void)
{
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow( xmin-SHIFT_FACTOR,  xmax-SHIFT_FACTOR,  ymin,  ymax);
}
void ShiftRight(void)
{
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow( xmin+SHIFT_FACTOR,  xmax+SHIFT_FACTOR,  ymin,  ymax);
}
void ShiftUp(void)
{
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow( xmin,  xmax,  ymin+SHIFT_FACTOR,  ymax+SHIFT_FACTOR);
}
void ShiftDown(void)
{
    float xmin, xmax, ymin, ymax;
    GetVisualizationWindow(&xmin, &xmax, &ymin, &ymax);
    SetVisualizationWindow( xmin,  xmax,  ymin-SHIFT_FACTOR,  ymax-SHIFT_FACTOR);
}
// 
static void MouseDrag(int winx, int winy)
{
    //printf("Mouse is moving with a button pressed\n");
    //printf("x=%d y=%d\n", x, y);
    xf=(float)winx;
    yf=(float)winy;
    if(isPanning)
    {
        Shift(xi, yi, xf, yf);
        xi=(float)xf;
        yi=(float)yf;
    }
    glutPostRedisplay();
}